using System;
using System.Reflection;
using System.Collections.Generic;

using SBPweb.Data.Generic;

namespace SBPweb.Data
{

	/// <summary>
	/// This class provides generic entity column registration and instantiation
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class EntityColumnFactory<T> : IDomainObjectFactory<T> where T : Entity
	{

		#region Private members

		private Dictionary<string, string> mFieldMappintDict;
		private static Dictionary<Type, ConstructorInfo> mConstructors = new Dictionary<Type, ConstructorInfo>();

		#endregion

		#region Constructor logic

		/// <summary>
		/// Initializes a new EntityColumnFactory instance
		/// </summary>
		public EntityColumnFactory()
		{
			mFieldMappintDict = new Dictionary<string, string>();

			Type type = typeof(T);

			if(!Entity.Columns.ContainsKey(type))
			{
				Entity.RegisterEntityTypeToColumns(type);
			}

			foreach (string fieldName in Entity.Columns[type])
			{
				mFieldMappintDict.Add(fieldName, fieldName);
			}
		}

		/// <summary>
		/// Initializes a new EntityColumnFactory instance
		/// </summary>
		/// <param name="fieldMappingDictionary">Existing field mapping dictionary</param>
		public EntityColumnFactory(Dictionary<string, string> fieldMappingDictionary)
		{
			mFieldMappintDict = fieldMappingDictionary;
		}

		#endregion

		#region Instantiation

		/// <summary>
		/// Creates a new instance of T with parameters
		/// </summary>
		/// <param name="parameters">parameters of the constructor of T</param>
		/// <returns></returns>
		internal static T CreateInstance(params object[] parameters)
		{
			ConstructorInfo cI = null;
			Type type = typeof(T);

			if (mConstructors.ContainsKey(type))
			{
				cI = mConstructors[type];
			}
			else
			{
				Type[] types = null;
				if (parameters != null)
				{
					List<Type> paramTypes = new List<Type>();
					foreach (object obj in parameters)
					{
						paramTypes.Add(obj.GetType());
					}
					if (paramTypes.Count > 0)
					{
						types = paramTypes.ToArray();
					}
				}
				else
				{
					types = new Type[] { };
				}
				cI = type.GetConstructor(types);

				if (cI != null)
				{
					lock (mConstructors)
					{
						mConstructors.Add(type, cI);
					}
				}
			}


			if (cI == null)
			{
				return default(T);
			}

			return (T)cI.Invoke(parameters);
		}

		#endregion

		#region IDomainObjectFactory<T> Members

		/// <summary>
		/// Reads up an instance of T from IDataReader
		/// </summary>
		/// <param name="reader">System.Data.IDataReader reader</param>
		/// <returns>an instance of T</returns>
		public T Construct(System.Data.IDataReader reader)
		{
			T ret = CreateInstance(null);

            if (ret == default(T))
            {
                throw new NullReferenceException(string.Format("Default constructor of {0} could not be found!", this.ToString()));
            }

			for(int i = 0; i < reader.FieldCount; i++)
			{
				string fieldName = reader.GetName(i);
				if (!reader.IsDBNull(i) && mFieldMappintDict.ContainsKey(fieldName))
				{
					ret.SetValue<object>(mFieldMappintDict[fieldName], reader.GetValue(i));
				}
			}

            ret.ActionState = EntityActionState.Unchanged;

			return ret;
		}

		#endregion
	}
}
